#!/usr/bin/env python

# (c) 2007-2011 Helsinki University of Technology and University of Helsinki
# Licensed under the MIT license; see LICENSE.txt for more information.

from rdf import *
import lxml.etree
from hashlib import md5
import re

class Document:

  def __init__(self, session, url, mimetype=None, data=None):
    self._session = session
    self._completed = set()
    self._metadata = {}	# key: RDF property, value: list of RDF nodes
    self._metadataGraph = getGraph()
    self._mimetype = mimetype
    self._data = data
    self._url = url
    self._succesful = False
    self._warnings = 0

    self._tree = None

  def getData(self):
    return self._data
  
  def getParseTree(self):
    # try to parse the data, if this is an HTML document
    if self._tree is None and self._mimetype == 'text/html':
      data = self.getData()
      if data is not None:
        datastr = data
        forceEncoding = self._session.getSource().getForceEncoding()
        if forceEncoding:
          self.log('NOTE', 'parse', 'Forcing HTML parser to use encoding', forceEncoding)
          datastr = datastr.decode(forceEncoding)
        try:
          self._tree = lxml.etree.HTML(datastr)
          if self._tree is None:
            self.log('CRIT', 'parse', 'Unable to parse HTML code', 'lxml.etree.HTML() returned None')
        except Exception, detail:
          self.log('CRIT', 'parse', 'Unable to parse HTML code', detail)

    return self._tree
  
  def releaseData(self):
    self._data = None
    self._tree = None

  def getURL(self):
    return self._url
  
  def getID(self):
    # NOTE encoding the URL as UTF-8 is a bit of a hack...
    m = md5()
    m.update(self._url.encode('UTF-8'))
    return m.hexdigest()
  
  def getIdentifier(self):
    ident = list(self._metadata.get(DC.identifier, [None]))[0]
    if ident is None:
      ident = URIRef(self._url)
    return ident

  def getIdentifierMD5(self):
    m = md5()
    m.update(self.getIdentifier().encode('UTF-8'))
    return m.hexdigest()
  
  def getTitle(self):
    title = self._metadata.get(DC['title'], '')
    if title and list(title)[0].strip() != '':
      return re.sub(r'(\S{40})', r'\1 ', list(title)[0])	# split overly long words to prevent line wrapping trouble
    if len(self._url) < 40:
      return self._url
    return self._url[:40] + "..."
  
  def getMetadata(self, prop, single=False):
    # get values from metadata dictionary
    vals = self._metadata.get(prop, set())
    
    # see if the graph contains more values; add them if any
    ident = self.getIdentifier()
    if ident is not None:
      for val in self._metadataGraph.objects(ident, prop):
        if val not in vals:
          vals.add(val)

    if single:
      if vals:
        return list(vals)[0]
      else:
        return None
    else:
      return vals
  
  def setMetadata(self, prop, val):
    self._metadata.setdefault(prop, set())
    self._metadata[prop].add(val)
  
  def setMetadataTriple(self, s, p, o):
    self._metadataGraph.add((s, p, o))
  
  def setMetadataNamespace(self, prefix, uri):
    self._metadataGraph.namespace_manager.bind(prefix, uri)
  
  def setMetadataInstance(self, prop, type, valprop, val, extraprops=None):
    node = getBNode()
    self._metadataGraph.add((node, RDF.type, type))
    self._metadataGraph.add((node, valprop, val))
    if extraprops is not None:
      for eprop, eval in extraprops.items():
        self._metadataGraph.add((node, eprop, eval))
    self.setMetadata(prop, node)
    
  def clearMetadata(self, prop, val=None):
    if self._metadata.has_key(prop):
      if val is None: # clear all values
        del self._metadata[prop]
      else: # clear only a specific value
        self._metadata[prop].discard(val)
    

    # make sure to clear values from _metadataGraph as well
    ident = self.getIdentifier()
    for obj in self._metadataGraph.objects(ident, prop):
      if val is None or val==obj:
        self._metadataGraph.remove((ident, prop, obj))
  
  def getMetadataGraph(self):
    if 'validate' not in self._completed:
      # this is done when creating reports for failed documents, which did not finish validation
      return self._metadataGraph

    # after validation the document should have an identifier,
    # so now the metadata may safely be added to the graph
    # using the identifier as subject for all triples
    ident = self.getIdentifier()
    for prop, vals in self._metadata.iteritems():
      for val in vals:
        self._metadataGraph.add((ident, prop, val))
    return self._metadataGraph
  
  def setCompleted(self, phase):
    self._completed.add(phase)
  
  def getCompleted(self):
    return self._completed
  
  def getSuccesful(self):
    return self._succesful
  
  def getWarnings(self):
    return self._warnings
  
  def log(self, level, phase, type, details=None):
#    print "LOG", level, phase, type, details
    if level == 'NOTE' and not self._session.getNotes():
      # NOTE level is ignored unless otherwise configured
      return
    self._session.getReport().log(level, phase, type, details, self)
    if level == 'CRIT' or level == 'SKIP':
      # critical error, no point in keeping the data and parse tree around
      # (saves memory)
      self.releaseData()
    if level == 'WARN':
      self._warnings += 1
    if level == 'SUCC':
      self._succesful = True
  
  def processingFinished(self):
    self._session.getReport().createDocumentReport(self)

  def __repr__(self):
    return "Document(url=%s)" % repr(self._url)
